﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.Text;
using System.Security.Cryptography;

namespace SmartDatingService {
    /// <summary>
    /// Operation Status.
    /// </summary>
    [DataContract]
    public enum OperationStatus {
        [EnumMember(Value = "Failure")]
        Failure,
        [EnumMember(Value = "Success")]
        Success,
        [EnumMember(Value = "InvalidEmail")]
        InvalidEmail,
        [EnumMember(Value = "InvalidEmailOrPassword")]
        InvalidEmailOrPassword,
        [EnumMember(Value = "InvalidSessionKey")]
        InvalidSessionKey,
        [EnumMember(Value = "NotAuthenticated")]
        NotAuthenticated,
        [EnumMember(Value = "AccountAlreadyExists")]
        AccountAlreadyExists,
        [EnumMember(Value = "NoPersonalData")]
        NoPersonalData,
        [EnumMember(Value = "NoLocation")]
        NoLocation,
        [EnumMember(Value = "NoRecipient")]
        NoRecipient,
        [EnumMember(Value = "MessageNotFound")]
        MessageNotFound,
        [EnumMember(Value = "IllegalDestination")]
        IllegalDestination,
        [EnumMember(Value = "InvalidSearchDescriptor")]
        InvalidSearchDescriptor,
        [EnumMember(Value = "DuplicateSearchCriteria")]
        DuplicateSearchCriteria,
        [EnumMember(Value = "SearchCriteriaNotFound")]
        SearchCriteriaNotFound,
        [EnumMember(Value = "NoCriteriasDefined")]
        NoCriteriasDefined,
        [EnumMember(Value = "NoMembersFound")]
        NoMembersFound,
        [EnumMember(Value = "UnknownSearchCriteria")]
        UnknownSearchCriteria
    }

    /// <summary>
    /// Provides operation result (of type T) and operation status code.
    /// If Status != OperationStatus.Success, Result is uninitialized
    /// </summary>
    /// <typeparam name="T">Return type.</typeparam>
    [DataContract]
    public struct ResultWithStatus<T> {
        [DataMember]
        public OperationStatus Status;

        [DataMember]
        public T Result;

        public static ResultWithStatus<T> NewErrorResult(OperationStatus status) {
            return new ResultWithStatus<T>() {
                Result = default(T),
                Status = status
            };
        }

        public static ResultWithStatus<T> NewSuccess(T result) {
            return new ResultWithStatus<T>() {
                Result = result,
                Status = OperationStatus.Success
            };
        }
    }

    [DataContract]
    public enum MessageFolder {
        [EnumMember(Value = "Inbox")]
        Inbox,
        [EnumMember(Value = "Draft")]
        Draft,  // reserved for future use
        [EnumMember(Value = "Sent")]
        Sent
    }

    [DataContract]
    public struct Pair<T, U> {
        [DataMember]
        public T First;
        [DataMember]
        public U Second;
    }

    /// <summary>
    /// Search result: personal data and optional location information.
    /// </summary>
    [DataContract]
    public struct SearchResult {
        [DataMember]
        public PersonalData pd;
        [DataMember]
        public double? latitude;
        [DataMember]
        public double? longitude;
    }

    /// <summary>
    /// Service Prototype.
    /// </summary>
    [ServiceContract(Namespace="http://zombator.ru/services/smartdating", Name="SmartDating")]
    public interface IServicePrototype {
        /// <summary>
        /// Perform Service-Side part of RSA Key Exchange: returns session key
        /// </summary>
        /// <param name="clientEmail">E-mail of the client with which to exchange keys with.
        /// If this parameter is "*", then key is issued for registration operation only.
        /// To authenticate after registration, client must perform another key-exchange.</param>
        /// <returns>Session key negotiation status</returns>
        [OperationContract]
        OperationStatus KeyExchange(string clientEmail, string clientKey);

        /// <summary>
        /// Authenticates client, identifying her by the session key hash and password encrypted with the session key
        /// </summary>
        /// <param name="encryptedPassword">Client password encrypted with the session key provided by KeyExchange operation</param>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <returns>Authentication Status</returns>
        [OperationContract]
        OperationStatus Authenticate(string sessionKeyHash, string encryptedPassword);

        /// <summary>
        /// Immediately terminates session with the specified hash of session key.
        /// Session key is invalidated after calling this
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <returns>Logout result</returns>
        [OperationContract]
        OperationStatus Logout(string sessionKeyHash);

        /// <summary>
        /// Registers the user with specified e-mail and password in the system.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="clientEmail">E-mail of the client being registered</param>
        /// <param name="encryptedPassword">Client password encrypted by the client session key</param>
        /// <returns>Registration result</returns>
        [OperationContract]
        OperationStatus Register(string sessionKeyHash, string clientEmail, string encryptedPassword);

        /// <summary>
        /// Unregisters the user with specified e-mail.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="clientEmail">E-mail of the client being deregistered</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus Unregister(string sessionKeyHash, string clientEmail);

        /// <summary>
        /// Creates or changes user's personal data to the data provided.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="personalData">New personal data for the user, encrypted with session key</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus SetPersonalData(string sessionKeyHash, string personalData);

        /// <summary>
        /// Returns user's personal data stored on server, encrypted with session key.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <returns>Encrypted user's personal data</returns>
        [OperationContract]
        ResultWithStatus<string> GetPersonalData(string sessionKeyHash);

        /// <summary>
        /// Returns image for the specified user.
        /// </summary>
        /// <param name="sessionHash">SHA1 hash of session key</param>
        /// <param name="userId">user's Id</param>
        /// <returns>user's image bytes</returns>
        [OperationContract]
        ResultWithStatus<string> GetImage(string sessionHash, Guid userId);

        /// <summary>
        /// Sets image for the current user
        /// </summary>
        /// <param name="sessionHash">sha1 hash of session key</param>
        /// <param name="imageContent">base64-encoded image content</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus SetImage(string sessionHash, string imageContent);

        /// <summary>
        /// Convenience method. Returns gender for the specified user.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="userId">user's Id</param>
        /// <returns>user's gender (0 = male, 1 = female, 2 = unspecified)</returns>
        [OperationContract]
        ResultWithStatus<int> GetGender(string sessionKeyHash, Guid userId);

        /// <summary>
        /// Sends a personal message with specified text
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="recipient">Recipient GUID, e.g. from results of member search</param>
        /// <param name="encryptedText">Text of the message, encrypted with the session key</param>
        /// <param name="encryptedSubject">Subject of the message, encrypted with the session key</param>
        /// <returns>status</returns>
        [OperationContract]
        OperationStatus SendPM(string sessionKeyHash, Guid recipient, string encryptedSubject, string encryptedText);

        /// <summary>
        /// Gets personal messages for the user
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="folder">Message folder</param>
        /// <returns>List of personal messages in the specified folder, sorted by date descending</returns>
        [OperationContract]
        ResultWithStatus<List<Msg>> GetPMs(string sessionKeyHash, MessageFolder folder);

        /// <summary>
        /// Gets a comma-delimited list of personal message ids in the specified folder,
        /// sorted by date descending.
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="folder">a comma-delimited string containing desired message IDs.
        /// Call GetPM(sessionKeyHash, id) to get message by its ID.</param>
        /// <returns></returns>
        [OperationContract]
        ResultWithStatus<string> GetPMIds(string sessionKeyHash, MessageFolder folder);

        /// <summary>
        /// Gets a PM with specified id.
        /// (Message must belong to the user's mailbox.)
        /// </summary>
        /// <param name="sessionKeyHash">user's session key hash</param>
        /// <param name="messageId">id of the message to get</param>
        /// <returns>msg</returns>
        [OperationContract]
        ResultWithStatus<Msg> GetPM(string sessionKeyHash, Guid messageId);

        /// <summary>
        /// Moves personal message from one folder to the other
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="messageId">Identifier of the message (from the Msg structure)</param>
        /// <param name="newFolder">Message folder to put the message into</param>
        /// <returns>status</returns>
        [OperationContract]
        OperationStatus MovePM(string sessionKeyHash, Guid messageId, MessageFolder newFolder);

        /// <summary>
        /// Deletes specified message from the message database
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="messageId">Identifier of the message from Msg structure</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus DeletePM(string sessionKeyHash, Guid messageId);

        /// <summary>
        /// Sets or updates current user location
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="newLatitude">New latitude in degrees. Negative values indicate N, positive S</param>
        /// <param name="newLongitude">New longitude in degrees. Negative values indicate W, positive E</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus SetLocation(string sessionKeyHash, double newLatitude, double newLongitude);

        [OperationContract]
        ResultWithStatus<Pair<double, double>?> GetLocation(string sessionKeyHash);

        /// <summary>
        /// Creates a new search
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <returns>search identifier</returns>
        [OperationContract]
        ResultWithStatus<Guid> CreateSearch(string sessionKeyHash);

        /// <summary>
        /// Removes search with specified id
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 session key hash</param>
        /// <param name="searchId">identifier of a search to delete</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus DestroySearch(string sessionKeyHash, Guid searchId);

        /// <summary>
        /// Add criteria to the search
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="searchId">Search identifier obtained by call to CreateSearch</param>
        /// <param name="criteria">Parameter being examined</param>
        /// <param name="encryptedCriteriaValue">Desired value</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus AddSearchCriteria(string sessionKeyHash, Guid searchId, string criteria, string encryptedCriteriaValue);

        /// <summary>
        /// Remove criteria from the search
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="searchId">Search identifier</param>
        /// <param name="criteria">Criteria being removed</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus RemoveSearchCriteria(string sessionKeyHash, Guid searchId, string criteria);

        /// <summary>
        /// Removes all search criterias from the specified search
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="searchId">search identifier</param>
        /// <returns></returns>
        [OperationContract]
        OperationStatus ClearSearchCriterias(string sessionKeyHash, Guid searchId);

        /// <summary>
        /// Returns the list of users matching search criterias, starting with the most
        /// closely located to the current user and most fitting the criteria(s)
        /// </summary>
        /// <param name="sessionKeyHash">SHA1 hash of session key</param>
        /// <param name="searchId">search identifier</param>
        /// <returns>list of user profiles, sorted by relevance</returns>
        [OperationContract]
        ResultWithStatus<SearchResult[]> GetSearchResults(string sessionKeyHash, Guid searchId);

        /// <summary>
        /// Returns neighbor IDs for the current user.
        /// Neighbors are people, whose distance from you does not exceed 1000m
        /// </summary>
        /// <param name="sessionKeyHash">sha1 hash of session key</param>
        /// <returns>comma-separated string list of guids of neighbors</returns>
        [OperationContract]
        ResultWithStatus<string> GetNeighborIds(string sessionKeyHash);

        /// <summary>
        /// Gets personal data for another user in XML string format
        /// </summary>
        /// <param name="sessionKeyHash">session key hash</param>
        /// <param name="userGuid">another user's guid</param>
        /// <returns>XML representation of PersonalData</returns>
        [OperationContract]
        ResultWithStatus<string> GetForeignPersonalData(string sessionKeyHash, Guid userGuid);
    }
}